package com.example.demo.boot.exception;

import com.alibaba.fastjson.JSON;
import com.example.demo.commons.result.common.entity.BizResult;
import com.example.demo.commons.result.common.enums.BaseErrorCodeEnum;
import com.example.demo.commons.result.common.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.method.annotation.ModelAttributeMethodProcessor;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.ServletException;
import java.text.MessageFormat;



/**
 * <p> 全局异常捕获（含业务异常捕获处理与运行时异常捕获打印到异常堆栈） </p>
 *
 * @author tanyunpeng
 * @date 2021/11/19
 */
@Slf4j(topic = "error")
@ControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger bizLog = LoggerFactory.getLogger("biz");

    private static final String REGEX_BLANK = "\\s*|\t|\r|\n";
    private static final String FORMAT_PARAM_INVALID = "[{0}:{1}]";
    private static final String FORMAT_PARAMS_ERROR_MSG = "参数转换错误：{0}={1} 值不存在或格式不正确！";

    /**
     * 组装拼接参数验证 @valid 错误信息
     *
     * @param bindingResult 绑定结果
     */
    private static StringBuilder joinErrorMsg(BindingResult bindingResult) {
        StringBuilder msg = new StringBuilder();
        bindingResult.getFieldErrors().forEach(err -> msg.append(
                MessageFormat.format(FORMAT_PARAM_INVALID, err.getField(), err.getDefaultMessage())));
        return msg;
    }

    /**
     * 获取bodyParam
     *
     * @param req HttpServletRequest缓存包装
     * @return bodyParam
     */
    private static String getBodyParam(ContentCachingRequestWrapper req) {
        return new String(req.getContentAsByteArray()).replaceAll(REGEX_BLANK, StringUtils.EMPTY);
    }

    /**
     * 获取参数
     *
     * @param req HttpServletRequest缓存包装
     * @return param
     */
    private static String getParam(ContentCachingRequestWrapper req) {
        return JSON.toJSONString(req.getParameterMap());
    }

    /**
     * 全局异常捕捉处理
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public BizResult defaultErrorHandler(ContentCachingRequestWrapper req, Exception ex) {
        //未处理异常，打印堆栈信息 使用阿里云日志服务监控报警
        log.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", ex.getMessage(),
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), ex);
        return BizResult.error(BaseErrorCodeEnum.UNKNOWN_ERROR);
    }

    /**
     * 业务异常捕捉处理
     *
     * @param req    HttpServletRequest缓存包装
     * @param bizExp 业务异常
     * @return 业务统一返回包装
     */
    @ResponseBody
    @ExceptionHandler(value = BizException.class)
    public BizResult bizExceptionHandler(ContentCachingRequestWrapper req, BizException bizExp) {
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", bizExp.getMessage(),
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), bizExp);
        return BizResult.error(bizExp);
    }

    /**
     * 参数不合法异常捕捉处理
     *
     * @param req       HttpServletRequest缓存包装
     * @param illArgExp 非法异常
     * @return 业务统一返回包装
     */
    @ResponseBody
    @ExceptionHandler(value = IllegalArgumentException.class)
    public BizResult illegalArgumentExceptionHandler(ContentCachingRequestWrapper req,
                                                     IllegalArgumentException illArgExp) {
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", illArgExp.getMessage(),
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), illArgExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR, illArgExp.getMessage());
    }

    /**
     * @param req          HttpServletRequest缓存包装
     * @param converterExp 转换异常
     * @return 业务统一返回包装
     * @see Converter 处理Converter参数转换异常
     */
    @ResponseBody
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    public BizResult methodArgumentTypeMismatchExceptionHandler(ContentCachingRequestWrapper req,
                                                                MethodArgumentTypeMismatchException converterExp) {
        String msg = MessageFormat.format(FORMAT_PARAMS_ERROR_MSG, converterExp.getName(), converterExp.getValue());
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", msg,
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), converterExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR, msg);
    }

    /**
     * @param req     HttpServletRequest缓存包装
     * @param bindExp 绑定异常
     * @return 业务统一返回包装
     * @see
     * @see ModelAttribute
     * @see ModelAttributeMethodProcessor#resolveArgument(org.springframework.core.MethodParameter,
     * org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest,
     * org.springframework.web.bind.support.WebDataBinderFactory)
     * <p>
     * 处理ModelAttribute参数绑定异常
     */
    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public BizResult bindExceptionHandler(ContentCachingRequestWrapper req, BindException bindExp) {
        StringBuilder msg = joinErrorMsg(bindExp.getBindingResult());
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", msg,
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), bindExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR, msg.toString());
    }

    /**
     * @param req         HttpServletRequest缓存包装
     * @param notValidExp 参数验证异常
     * @return 业务统一返回包装
     * @see
     * @see RequestBody 处理RequestBody参数验证异常
     */
    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public BizResult methodArgumentNotValidExceptionHandler(ContentCachingRequestWrapper req,
                                                            MethodArgumentNotValidException notValidExp) {
        StringBuilder msg = joinErrorMsg(notValidExp.getBindingResult());
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", msg,
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), notValidExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR, msg.toString());
    }

    /**
     * @param req HttpServletRequest缓存包装
     * @return 业务统一返回包装
     */
    @ResponseBody
    @ExceptionHandler(value = ServletException.class)
    public BizResult servletExceptionHandler(ContentCachingRequestWrapper req, ServletException servletExp) {
        bizLog.error("全局业务异常捕获:{},url:{},bodyParam:{},param:{}", servletExp.getMessage(),
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), servletExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR, servletExp.getMessage());
    }

    /**
     * @param req HttpServletRequest缓存包装
     * @return 业务统一返回包装
     */
    @ResponseBody
    @ExceptionHandler(value = HttpMessageNotReadableException.class)
    public BizResult httpMessageNotReadableExceptionHandler(ContentCachingRequestWrapper req,
                                                            HttpMessageNotReadableException httpExp) {
        bizLog.error("全局业务异常捕获:HttpMessageNotReadableException,url:{},bodyParam:{},param:{}",
                req.getRequestURL().toString(), getBodyParam(req), getParam(req), httpExp);
        return BizResult.error(BaseErrorCodeEnum.PARAMS_ERROR);
    }
}